namespace OptionParser
{
    using System;
    using System.Reflection;
    using System.Collections;
    
    public class GetOptions
    {
        private ArrayList arg_list;
        private string usage;
        private string version_string;
        
        [Option("-h", "Show help")]
        public bool Help;
        
        [Option("-V", "Show version information")]
        public bool Version;
        
        public GetOptions()
        {
        }
        
        public virtual void ProcessArgs(string[] args)
        {
            this.arg_list = new ArrayList(args);
                        
            Type opt = this.GetType();
            Assembly a = Assembly.GetEntryAssembly();
                        
            string title = GetTitle(a);
            string app_name = a.GetName().Name + ".exe";
            string desc = GetDesc(a);
            string version = a.GetName().Version.ToString();
            string copy = "Copyright " + GetCopyright(a);
            usage += String.Format("{0}: {1}\nUsage: {2} [options]\n", title, desc, app_name);
            version_string = String.Format("{0} Version {1}\n{2}", title, version, copy);
            
            FieldInfo[] fields = opt.GetFields();
            for (int i = 0; i < fields.Length; i++)
            {
                try
                {
                    object[] atts = fields[i].GetCustomAttributes(false);
                    OptionAttribute att = atts[0] as OptionAttribute;
                    
                    usage += BuildUsage(fields[i], att.Flag, att.Description, fields[i].GetValue(this));                    
                                        
                    // check args for flag
                    int index = FindFlag(att.Flag);
                    if (index > -1) // found one
                    {
                        if (fields[i].FieldType == typeof(bool))
                        {
                            fields[i].SetValue(this, true);
                            arg_list.RemoveAt(index);
                        }
                        else if (fields[i].FieldType == typeof(int))
                        {
                            fields[i].SetValue(this, Int32.Parse((string)arg_list[index+1]));
                            arg_list.RemoveAt(index);
                            arg_list.RemoveAt(index);
                        }
                        else if (fields[i].FieldType == typeof(double))
                        {
                            fields[i].SetValue(this, Double.Parse((string)arg_list[index+1]));
                            arg_list.RemoveAt(index);
                            arg_list.RemoveAt(index);
                        }
                        else if (fields[i].FieldType == typeof(float))
                        {
                            fields[i].SetValue(this, Single.Parse((string)arg_list[index+1]));
                            arg_list.RemoveAt(index);
                            arg_list.RemoveAt(index);
                        }
                        else if (fields[i].FieldType == typeof(string))
                        {
                            fields[i].SetValue(this, (string)arg_list[index+1]);
                            arg_list.RemoveAt(index);
                            arg_list.RemoveAt(index);
                        }
                        else if(fields[i].FieldType.IsEnum)
                        {
                            fields[i].SetValue(this, Enum.Parse(fields[i].FieldType, (string)arg_list[index+1], true));
                            arg_list.RemoveAt(index);
                            arg_list.RemoveAt(index);
                        }
                    }
                        
                }
                catch (IndexOutOfRangeException)
                {
                    throw new OptionException(String.Format("No attribute on {0} field {1}", opt.Name, fields[i].Name));
                }
                catch (NullReferenceException)
                {
                    throw new OptionException(String.Format("Incorrect attribute on {0} field {1}", opt.Name, fields[i].Name));
                }
                catch (FormatException)
                {
                   throw new OptionException(String.Format("Can't parse option on {0} field {1}", opt.Name, fields[i].Name));
                }
            }
            
            // this has been burning me, so now check rest of remaingin args and throw an exception 
            // on any unmatched string containing "-" as the first char
            foreach (string s in arg_list)
            {
                if (s[0] == '-')
                    throw new OptionException(String.Format("Can't find option {0}; maybe you meant something else?", s));
            }
            
            if (Help)
            {
                Console.WriteLine(usage);
                Environment.Exit(0);
            }
            
            if (Version)
            {
                Console.WriteLine(version_string);
                Environment.Exit(0);
            }                
        }
        
        public string[] RemainingArgs
        {
            get { return (string[])arg_list.ToArray(typeof(string)); }
        }
        
        private static string GetTitle(Assembly a)
        {
            object[] atts = a.GetCustomAttributes(false);

            string title = "No title";
            foreach (object o in atts)
            {
                if (o.GetType() == typeof(AssemblyTitleAttribute))
                    title = ((AssemblyTitleAttribute)o).Title;
            }
            
            return title;
        }
        
        private static string GetDesc(Assembly a)
        {
            object[] atts = a.GetCustomAttributes(false);

            string desc = "No description";
            foreach (object o in atts)
            {
                if (o.GetType() == typeof(AssemblyDescriptionAttribute))
                    desc = ((AssemblyDescriptionAttribute)o).Description;
            }
            
            return desc;
        }
        
        private static string GetCopyright(Assembly a)
        {
            object[] atts = a.GetCustomAttributes(false);

            string copy = "No copyright";
            foreach (object o in atts)
            {
                if (o.GetType() == typeof(AssemblyCopyrightAttribute))
                    copy = ((AssemblyCopyrightAttribute)o).Copyright;
            }
            
            return copy;
        }       
        
        private static string BuildUsage(FieldInfo f, string flag, string desc, object o)
        {
            if (f.FieldType == typeof(bool))
                return String.Format("  {0}          {1}\n", flag, desc);
            else if (f.FieldType == typeof(string))
                return String.Format("  {0} string   {1} ({2})\n", flag, desc, o);
            else if (f.FieldType.IsEnum)
            {
                string ret = String.Format("  {0} string   {1}.  ", flag, desc);
                ret +=                     "Possible strings are:\n                 ";
                foreach (FieldInfo info in f.FieldType.GetFields(BindingFlags.Static|BindingFlags.Public|BindingFlags.DeclaredOnly))
                    ret += " " + info.Name;
                ret += " (default: " + o.ToString() + ")\n";
                return ret;
            }
            else
                return String.Format("  {0} #        {1} ({2})\n", flag, desc, o);
        }
        
        private int FindFlag(string flag)
        {
            int index = -1;
            for (int i = 0; i < arg_list.Count; i++)
            {
                if (String.Compare(flag, (string)arg_list[i]) == 0)
                {
                    index = i;
                    break;
                }
            }
            
            return index;
        }
    }
}
